package msg.servlet;

import static org.junit.Assert.*;

import java.net.HttpURLConnection;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.*;

import msg.*;

import org.apache.wink.client.*;
import org.json.*;
import org.junit.*;
import org.junit.Test;

import registration.RegistrationManager;
import user.LoginManager;
import user.servlet.UsersResource;

import common.Pair;
import common.dal.Transaction;
import common.exceptions.InvalidUserException;
import common.servlet.ServletTests;

@SuppressWarnings({"javadoc", "static-method"})
public class MessageResourceTest extends ServletTests {
	private static String	MESSAGES;
	protected long			cookie;
	
	@BeforeClass
	public static void setupBeforeClass() throws Exception {
		ServletTests.setupBeforeClass();
		MESSAGES = getResources() + "messages";
	}
	
	@Override
	public void setupServlets() throws Exception {
		RegistrationManager.getNewInstance(t).clear();
		MessageManager.getNewInstance(t).clear();
		t.commit();
	}
	
	@Before
	public void setupCookie() throws Exception {
		cookie = loginUser();
	}
	
	@After
	public void tearDownCookie() throws Exception {
		logoutUser();
	}
	
	@Test
	public void testPost() throws Exception {
		// post with no cookie
		Resource res = rc.resource(MESSAGES);
		JSONObject request = getDefaultRequest();
		ClientResponse result = res.post(request);
		assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, result.getStatusCode());
		result = res.cookie(createCookie("101")).post(request);
		// post with bad cookie
		assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, result.getStatusCode());
		// post with real cookie
		result = res.cookie(createCookie(cookie)).post(request);
		assertEquals(HttpURLConnection.HTTP_CREATED, result.getStatusCode());
		assertEquals(MESSAGES + "/0", result.getHeaders().get("Location").get(0));
	}
	
	@Test
	public void testRetrieve() throws Exception {
		// check non-existing resource
		Resource res = rc.resource(MESSAGES + "/101");
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		// check existing resource
		long id = postMessage();
		response = rc.resource(MESSAGES + "/" + id).get();
		JSONObject responseObject = response.getEntity(JSONObject.class);
		responseObject.remove("time"); // no way to determine time for testing
		JSONObject request = getDefaultRequest();
		request.put("id", id);
		assertEquals(request, responseObject);
		// make sure events aren't treated as messages
		Long eventId = postEvent().first;
		response = rc.resource(MESSAGES + "/" + eventId).get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
	}
	
	@Test
	public void testList() throws Exception {
		// check non-existing resource
		Resource res = rc.resource(MESSAGES);
		ClientResponse response = res.get();
		JSONArray array = new JSONArray();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		long id = postMessage();
		// check existing resource
		response = res.get();
		JSONArray responseObject = response.getEntity(JSONArray.class);
		responseObject.getJSONObject(0).remove("time"); // no way to determine time for testing
		JSONObject request = getDefaultRequest();
		request.put("id", id);
		array.put(request);
		assertEquals(array, responseObject);
		// make sure events aren't listed
		postEvent();
		response = res.get();
		responseObject = response.getEntity(JSONArray.class);
		responseObject.getJSONObject(0).remove("time"); // no way to determine time for testing
		assertEquals(array, responseObject);
	}
	
	@Test
	public void testDelete() throws Exception {
		try {
			// delete with no cookie
			Resource res = rc.resource(MESSAGES + "/101");
			ClientResponse response = res.delete();
			assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, response.getStatusCode());
			// delete with existing cookie, non-existing message
			response = res.cookie(createCookie(cookie)).delete();
			assertEquals(HttpURLConnection.HTTP_NO_CONTENT, response.getStatusCode());
			// delete using non-existing cookie, an existing message
			long id = postMessage();
			res = rc.resource(MESSAGES + "/" + id).header("Cookie", UsersResource.COOKIE_NAME + "="
					+ (cookie + 100));
			Thread.sleep(100);
			response = res.delete();
			assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, response.getStatusCode());
			// delete using an existing cookie, a message that does not belong to the cookie
			t = new Transaction();
			LoginManager.getNewInstance(t).registerUser(getFakeUser().getLogin(), "whatever", PASSWORD);
			long newCookie = LoginManager.getNewInstance(t).loginUser(getFakeUser().getLogin(), PASSWORD).getId();
			t.commit();
			response = res.cookie(createCookie(newCookie)).delete();
			assertEquals(HttpURLConnection.HTTP_FORBIDDEN, response.getStatusCode());
			// delete using correct cookie
			response = res.cookie(createCookie(cookie)).delete();
			assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
			// assure is deleted
			response = res.delete();
			assertEquals(HttpURLConnection.HTTP_NO_CONTENT, response.getStatusCode());
			response = res.get();
			assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
			// can't delete events
			Long eventId = postEvent().first;
			response = rc.resource(MESSAGES + "/" + eventId).cookie(createCookie(cookie)).delete();
			assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		} finally {
			t = new Transaction();
			LoginManager.getNewInstance(t).deleteUser(getFakeUser().getLogin(), PASSWORD);
			t.commit();
		}
	}
	
	@Test
	public void testAddressSaved() throws Exception {
		Resource res = rc.resource(MESSAGES).cookie(createCookie(cookie));
		JSONObject request = getDefaultRequest();
		request.getJSONObject("location").put("address", "Internationale");
		ClientResponse response = res.post(request);
		assertEquals(HttpURLConnection.HTTP_CREATED, response.getStatusCode());
		
		response = rc.resource(response.getHeaders().getFirst("location")).get();
		assertEquals(request.remove("time"), response.getEntity(JSONObject.class).remove("id").remove("time"));
	}
	
	@Test
	public void testRadius() throws Exception {
		// test empty
		Resource res = rc.resource(MESSAGES + getInRadiusString());
		assertEquals(new JSONArray(), res.get().getEntity(JSONArray.class));
		assertEquals(new JSONArray(), res.get().getEntity(JSONArray.class));
		// add a new event
		t = new Transaction();
		MessageManager.getNewInstance(t).createNewEventMessage(netanya, "event", getUser(), getTomorrow(), 10);
		// add new messages
		Message haifaMsg = MessageManager.getNewInstance(t).createNewMessage(haifa, "event", getUser());
		Message taMsg = MessageManager.getNewInstance(t).createNewMessage(telAviv, "event", getUser());
		t.commit();
		res = rc.resource(MESSAGES
				+ getInRadiusString(netanya, new Location(netanya.getLatitude() - 0.05,
						netanya.getLongitude() - 0.05), new Location(netanya.getLatitude() + 0.05, netanya
						.getLongitude() + 0.05)));
		assertEquals(new JSONArray(), res.get().getEntity(JSONArray.class));
		res = rc.resource(MESSAGES
				+ getInRadiusString(haifa, new Location(haifa.getLatitude() - 0.05, haifa.getLongitude() - 0.05), new Location(
						haifa.getLatitude() + 0.05, haifa.getLongitude() + 0.05)));
		assertEquals(new JSONArray(Arrays.asList(haifaMsg.accept(jsoner))), res.get().getEntity(JSONArray.class));
		res = rc.resource(MESSAGES
				+ getInRadiusString(netanya, new Location(netanya.getLatitude() - 0.5,
						netanya.getLongitude() - 0.5), new Location(netanya.getLatitude() + 0.5, netanya
						.getLongitude() + 0.5)));
		res = rc.resource(MESSAGES + getInRadiusString(netanya, haifa, haifa));
		assertEquals(new JSONArray(Arrays.asList(haifaMsg.accept(jsoner), taMsg.accept(jsoner))), res.get()
				.getEntity(JSONArray.class));
	}
	
	@Test
	public void testSince() throws Exception {
		t = new Transaction();
		Message msg = MessageManager.getNewInstance(t)
				.createNewMessage(new Location(0.5, 0.5), "oldMessage", getUser());
		Calendar firstMsgTime = Calendar.getInstance();
		firstMsgTime.setTimeInMillis(msg.getTime());
		Thread.sleep(10);
		Message newMsg = MessageManager.getNewInstance(t)
				.createNewMessage(new Location(0.5, 0.5), "newMessage", getUser());
		MessageManager.getNewInstance(t)
				.createNewEventMessage(msg.getLocation(), "event", getUser(), getTomorrow(), 10);
		t.commit();
		long since = firstMsgTime.getTimeInMillis() + 5;
		Resource res = rc.resource(MESSAGES + getInRadiusString() + "&since=" + since);
		assertEquals(new JSONArray(Arrays.asList(newMsg.accept(jsoner))), res.get().getEntity(JSONArray.class));
		since = firstMsgTime.getTimeInMillis();
		res = rc.resource(MESSAGES + getInRadiusString() + "&since=" + since);
		assertEquals(new JSONArray(Arrays.asList(msg.accept(jsoner), newMsg.accept(jsoner))), res.get()
				.getEntity(JSONArray.class));
		res = rc.resource(MESSAGES + getInRadiusString() + "&since=" + new Date().getTime());
		assertEquals(new JSONArray(), res.get().getEntity(JSONArray.class));
	}
	
	@Test
	public void testNaN() throws Exception {
		Resource res = rc.resource(MESSAGES + "/inRadius?lat=41.85876348524552&long=-91.88570312499996"
				+ "&n=-29.747031249999964&s=-154.02437499999996"
				+ "&w=23.169155984778328&e=60.548370985712715&past=false");
		ClientResponse result = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, result.getStatusCode());
	}
	
	protected long postMessage() {
		Resource res = rc.resource(MESSAGES);
		JSONObject request = getDefaultRequest();
		ClientResponse result = res.cookie(createCookie(cookie)).post(request);
		assertEquals(HttpURLConnection.HTTP_CREATED, result.getStatusCode());
		String[] location = result.getHeaders().get("Location").get(0).split("/");
		return Long.parseLong(location[location.length - 1]);
	}
	
	public static Pair<Long, Long> postEvent() throws SQLException, InvalidUserException {
		t = new Transaction();
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(1, 0.5), "new event", getUser(), getTomorrow(), 10);
		t.commit();
		return Pair.of(event.getId(), event.getTime());
	}
	
	protected JSONObject getDefaultRequest() {
		JSONObject request = new JSONObject();
		request.put("content", "new message content");
		JSONObject location = new JSONObject();
		location.put("lat", 1);
		location.put("long", 0.5);
		request.put("location", location);
		request.put("poster", getUser().getId());
		request.put("name", getUser().getName());
		return request;
	}
	
	private void logoutUser() {
		Resource res = rc.resource(getResources() + "users/login/" + getUser().getId());
		ClientResponse response = res.cookie(createCookie(cookie)).delete();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
	}
	
	// returns the session id
	private static long loginUser() throws Exception {
		Resource res = rc.resource(getResources() + "users/login/" + getUser().getId());
		ClientResponse response = res.post(new JSONObject("{password: " + PASSWORD + "}"));
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		return response.getEntity(JSONObject.class).getLong("cookie");
	}
	
	protected String getInRadiusString(Location center, Location sw, Location ne) {
		return MessageFormat
				.format("/inRadius?lat={0,number,#.######}&long={1,number,#.######}"
						+ "&s={2,number,#.######}&w={3,number,#.######}"
						+ "&n={4,number,#.######}&e={5,number,#.######}", center.getLatitude(), center
						.getLongitude(), sw.getLongitude(), sw.getLatitude(), ne.getLongitude(), ne.getLatitude());
	}
	
	// whole world
	protected String getInRadiusString() {
		return getInRadiusString(new Location(0, 0), SW_MAX, NE_MAX);
	}
}
